//--------------------------------------------------------------------------------------
// File: HeightMap.cpp
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//--------------------------------------------------------------------------------------
#include "DXUT.h"
#include "resource.h"
#include <tiffio.h>

#include "voxOctree.h"

LPDIRECT3DTEXTURE9          g_pFrameTexture = 0;
ID3DXSprite*                g_pSpriteTex = NULL;

#define WIDTH 640
#define HEIGHT 480

//--------------------------------------------------------------------------------------
// Rejects any D3D9 devices that aren't acceptable to the app by returning false
//--------------------------------------------------------------------------------------
bool CALLBACK IsD3D9DeviceAcceptable( D3DCAPS9* pCaps, D3DFORMAT AdapterFormat, D3DFORMAT BackBufferFormat,
                                      bool bWindowed, void* pUserContext )
{
    // Typically want to skip back buffer formats that don't support alpha blending
    IDirect3D9* pD3D = DXUTGetD3D9Object();
    if( FAILED( pD3D->CheckDeviceFormat( pCaps->AdapterOrdinal, pCaps->DeviceType,
                                         AdapterFormat, D3DUSAGE_QUERY_POSTPIXELSHADER_BLENDING,
                                         D3DRTYPE_TEXTURE, BackBufferFormat ) ) )
        return false;

    return true;
}


//--------------------------------------------------------------------------------------
// Before a device is created, modify the device settings as needed
//--------------------------------------------------------------------------------------
bool CALLBACK ModifyDeviceSettings( DXUTDeviceSettings* pDeviceSettings, void* pUserContext )
{
    return true;
}


//--------------------------------------------------------------------------------------
// Create any D3D9 resources that will live through a device reset (D3DPOOL_MANAGED)
// and aren't tied to the back buffer size
//--------------------------------------------------------------------------------------
HRESULT CALLBACK OnD3D9CreateDevice( IDirect3DDevice9* pd3dDevice, const D3DSURFACE_DESC* pBackBufferSurfaceDesc,
                                     void* pUserContext )
{    
    // get image info and store depth into an array
    UINT32 height=0, width=0;
    INT16* depthArray = 0;
    INT16 min_h=0, max_h=0;

    TIFF* pImg = TIFFOpen("..\\media\\height_maps\\30n000e_20101117_gmted_mea075.tif", "r");
    if (pImg)
    {
        TIFFGetField(pImg, TIFFTAG_IMAGEWIDTH, &width);
        TIFFGetField(pImg, TIFFTAG_IMAGELENGTH, &height);

        UINT16 bitsPerChannel=0, samplePerPixel=0;
        TIFFGetField(pImg, TIFFTAG_BITSPERSAMPLE, &bitsPerChannel);
        TIFFGetField(pImg, TIFFTAG_SAMPLESPERPIXEL, &samplePerPixel);

        UINT32 lineSize = TIFFScanlineSize(pImg);
        tdata_t buff = _TIFFmalloc(lineSize);
        depthArray = new INT16[height*width];

        if(buff)
        {
            for (int row = 0; row < height; row++)
            {
                TIFFReadScanline(pImg, buff, row, 0);
                INT16* temp = (INT16*)buff;

                for(int i=0; i<width; i++)
                {
                    depthArray[width*row + i] = temp[i];
                    max_h = max(temp[i], max_h);
                    min_h = min(temp[i], min_h);
                }
            }

            _TIFFfree(buff);
        }

        TIFFClose(pImg);
    }
    else
        MessageBoxA(0, "fail to open tiff", "file error", 0);

    D3DXCreateTexture(pd3dDevice, WIDTH, HEIGHT, 1, D3DUSAGE_DYNAMIC, D3DFMT_A32B32G32R32F, D3DPOOL_DEFAULT, &g_pFrameTexture);
    D3DLOCKED_RECT locked_rect;
    if( SUCCEEDED(g_pFrameTexture->LockRect(0, &locked_rect, 0, D3DLOCK_NO_DIRTY_UPDATE)))
    {
        for (int y=0; y<HEIGHT; y++)
        {
            for (int x=0; x<WIDTH; x++)
            {
                float* pPixel = (float*)locked_rect.pBits + locked_rect.Pitch*y/4 + x*4;
                INT16 depth_i = depthArray[width*y*(height/HEIGHT) + x*(width/WIDTH)];
                float depth_f = float(depth_i-min_h)/(max_h - min_h);
                pPixel[0] = pPixel[1] = pPixel[2] = depth_f;
                if (depth_i <0) { pPixel[0] = 1; pPixel[1]=pPixel[2]=0; }
                if (depth_i == 0) { pPixel[1] = 1; pPixel[0]=pPixel[2]=0; }
            }
        }
        g_pFrameTexture->UnlockRect(0);
    }
    

    // earth radius: 6371 km
    // west-east 30
    // south-north 20
    // min -81
    // max 4712
    //CreateVoxelTerrainFromHeightMap(UINT16* phMap, )
    SAFE_DELETE_ARRAY(depthArray);
    return S_OK;
}

// height map with UINT16 format by default
// resolution of the height map
// the corresponding 3D volume to build the terrain
bool CreateVoxelOctreeFromHeightFunction(UINT16* pHMap, UINT32 mapWidth, UINT32 mapHeight, float min_x, float max_x, float min_y, float max_y, float min_z, float max_z)
{
    // find out the physical size of the octree
    float fSize_x = max_x - min_x;
    float fSize_y = max_y - min_y;
    float fSize_z = max_z - min_z;
    float fSize = max(max(fSize_x, fSize_y), fSize_z);

    // find out the level of the tree
    int level=0;
    UINT32 res = max(mapHeight, mapWidth);
    while(pow(2.0, level)<res)
        level++;

    VoxNode* pRoot = new VoxNode();

    VoxOctree tree;

    tree.
}   

void splitNode(UINT16* pHMap, UINT32 mapWidth, UINT32 mapHeight, float min_x, float max_x, float min_y, float max_y, float min_z, float max_z, )
//--------------------------------------------------------------------------------------
// Create any D3D9 resources that won't live through a device reset (D3DPOOL_DEFAULT) 
// or that are tied to the back buffer size 
//--------------------------------------------------------------------------------------
HRESULT CALLBACK OnD3D9ResetDevice( IDirect3DDevice9* pd3dDevice, const D3DSURFACE_DESC* pBackBufferSurfaceDesc,
                                    void* pUserContext )
{
    HRESULT hr;
    V_RETURN( D3DXCreateSprite( pd3dDevice, &g_pSpriteTex ) );
    return S_OK;
}


//--------------------------------------------------------------------------------------
// Handle updates to the scene.  This is called regardless of which D3D API is used
//--------------------------------------------------------------------------------------
void CALLBACK OnFrameMove( double fTime, float fElapsedTime, void* pUserContext )
{
}


//--------------------------------------------------------------------------------------
// Render the scene using the D3D9 device
//--------------------------------------------------------------------------------------
void CALLBACK OnD3D9FrameRender( IDirect3DDevice9* pd3dDevice, double fTime, float fElapsedTime, void* pUserContext )
{
    HRESULT hr;

    // Clear the render target and the zbuffer 
    V( pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_ARGB( 0, 45, 50, 170 ), 1.0f, 0 ) );

    // Render the scene
    if( SUCCEEDED( pd3dDevice->BeginScene() ) )
    {
        if( SUCCEEDED(g_pSpriteTex->Begin(0)))
        {
            D3DXMATRIX mat;
            D3DXMatrixTransformation2D(&mat, 0, 1, 0, 0, 0, 0);

            g_pSpriteTex->SetTransform(&mat);
            V(g_pSpriteTex->Draw(g_pFrameTexture, 0,0,0,0xFFFFFFFF ));
            g_pSpriteTex->End();
        }

        V( pd3dDevice->EndScene() );
    }
}


//--------------------------------------------------------------------------------------
// Handle messages to the application 
//--------------------------------------------------------------------------------------
LRESULT CALLBACK MsgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
                          bool* pbNoFurtherProcessing, void* pUserContext )
{
    return 0;
}


//--------------------------------------------------------------------------------------
// Release D3D9 resources created in the OnD3D9ResetDevice callback 
//--------------------------------------------------------------------------------------
void CALLBACK OnD3D9LostDevice( void* pUserContext )
{
    SAFE_RELEASE( g_pSpriteTex );
    SAFE_RELEASE( g_pFrameTexture);
}


//--------------------------------------------------------------------------------------
// Release D3D9 resources created in the OnD3D9CreateDevice callback 
//--------------------------------------------------------------------------------------
void CALLBACK OnD3D9DestroyDevice( void* pUserContext )
{
    SAFE_RELEASE( g_pSpriteTex );
    SAFE_RELEASE( g_pFrameTexture);
}


//--------------------------------------------------------------------------------------
// Initialize everything and go into a render loop
//--------------------------------------------------------------------------------------
INT WINAPI wWinMain( HINSTANCE, HINSTANCE, LPWSTR, int )
{
    // Enable run-time memory check for debug builds.
#if defined(DEBUG) | defined(_DEBUG)
    _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
#endif

    // Set the callback functions
    DXUTSetCallbackD3D9DeviceAcceptable( IsD3D9DeviceAcceptable );
    DXUTSetCallbackD3D9DeviceCreated( OnD3D9CreateDevice );
    DXUTSetCallbackD3D9DeviceReset( OnD3D9ResetDevice );
    DXUTSetCallbackD3D9FrameRender( OnD3D9FrameRender );
    DXUTSetCallbackD3D9DeviceLost( OnD3D9LostDevice );
    DXUTSetCallbackD3D9DeviceDestroyed( OnD3D9DestroyDevice );
    DXUTSetCallbackDeviceChanging( ModifyDeviceSettings );
    DXUTSetCallbackMsgProc( MsgProc );
    DXUTSetCallbackFrameMove( OnFrameMove );

    // TODO: Perform any application-level initialization here

    // Initialize DXUT and create the desired Win32 window and Direct3D device for the application
    DXUTInit( true, true ); // Parse the command line and show msgboxes
    DXUTSetHotkeyHandling( true, true, true );  // handle the default hotkeys
    DXUTSetCursorSettings( true, true ); // Show the cursor and clip it when in full screen
    DXUTCreateWindow( L"HeightMap" );
    DXUTCreateDevice( true, WIDTH, HEIGHT );

    // Start the render loop
    DXUTMainLoop();

    // TODO: Perform any application-level cleanup here

    return DXUTGetExitCode();
}


